
#include <BFile>
#include <BObcDocument>
#include "member_BObcDocument.h"

using namespace BWE;

#define member					(*(member_BObcDocument*)_ptr)
#define member_allocate()		_ptr = new member_BObcDocument(this)
#define member_release()		delete (member_BObcDocument*)_ptr

#define SignName(SIGN)		_signNames[SIGN]=#SIGN;
#define SignRealName(SIGN)	_signNames[SIGN]=#SIGN;	SignName(SIGN##_D)
#define RegistObjectSign(type, sign) regist(B##type::TypeName(), sign, #sign, (readFunc)&BObcDocument::read##type, (writeFunc)&BObcDocument::write##type)

BObcDocument::AutoChunk::AutoChunk(const BObcDocument* obcdoc, unsigned short sign)
{
	this->obcdoc = obcdoc;
	obcdoc_member(obcdoc)->beginChunk(sign);
	BArray<ChunkInfo>& chunkStack = obcdoc_member(obcdoc)->chunkStack;
	this->sign = chunkStack.last().sign;
}
BObcDocument::AutoChunk::~AutoChunk()
{
	obcdoc_member(obcdoc)->endChunk(sign);
}

BObcDocument::BObcDocument()
{
	member_allocate();
}
BObcDocument::~BObcDocument()
{
	member_release();
}

BObject* BObcDocument::create(BUShort sign) const
{
	switch (sign)
	{
	case SIGN_SCENE:		return new BScene();
	case SIGN_NODE:			return new BNode();
	case SIGN_LIGHT:		return new BLight();
	case SIGN_CAMERA:		return new BCamera();
	case SIGN_GROUP:		return new BGroup();

	case SIGN_MESH:			return new BMesh();
	case SIGN_GEOMETRY:		return new BGeometry();
	case SIGN_ELEMENT:		return new BElement();
	case SIGN_CONE:			return new BCone();
	case SIGN_CUBE:			return new BCube();
	case SIGN_CYLINDER:		return new BCylinder();
	case SIGN_SPHERE:		return new BSphere();
	case SIGN_GEOSPHERE:	return new BGeoSphere();
	case SIGN_TORUS:		return new BTorus();
	case SIGN_TUBE:			return new BTube();
	case SIGN_SURFACE:		return new BSurface();
	case SIGN_TERRAIN:		return new BTerrain();
	case SIGN_BILLBOARD:	return new BBillboard();
	case SIGN_PARTICLE:		return new BParticle();
	case SIGN_CAPSULE:		return new BCapsule();

	case SIGN_CONSTRAINT:			return new BConstraint();
	case SIGN_POINT_CONSTRAINT:		return new BPointConstraint();
	case SIGN_HINGE_CONSTRAINT:		return new BHingeConstraint();
	case SIGN_SLIDER_CONSTRAINT:	return new BSliderConstraint();
	case SIGN_CONEAR_CONSTRAINT:	return new BConearConstraint();
	case SIGN_SIXDOF_CONSTRAINT:	return new BSixdofConstraint();

	case SIGN_FONT:			return new BFont();
	case SIGN_STYLE:		return new BStyle();
	case SIGN_IMAGE:		return new BImage();
	case SIGN_PIXMAP:		return new BPixmap();

	case SIGN_MATERIAL:		return new BMaterial();
	case SIGN_PROGRAM:		return new BProgram();
	case SIGN_SHADER:		return new BShader();
	case SIGN_UNIFORM:		return new BUniform();
	case SIGN_PHYSICS:		return new BPhysics();
	case SIGN_SCRIPT:		return new BScript();
	case SIGN_SOURCE:		return new BSource();

	default:
		break;
	}
	return 0;
}

void BObcDocument::regist(const BString& type, BUShort sign, readChunk read, writeChunk write)
{
	member.readChunkMap[type] = read;
	member.writeChunkMap[type] = write;
	member.typeSignMap[type] = sign;
	member.signTypeMap[sign] = type;
}

BObject* BObcDocument::load(const BString& fileName)
{
	BFile file(fileName);
	if (!file.open(IO_Read))
	{
		message() << "OBC Error : Read file failed : Can't open file " << fileName;
		return 0;
	}
	return read(file);
}
bool BObcDocument::load(const BString& fileName, BObject* object)
{
	BFile file(fileName);
	if (!file.open(IO_Read))
	{
		member.message() << "OBC Error : Read file failed : Can't open file " << fileName;
		return false;
	}
	return read(file, object);
}
bool BObcDocument::save(const BString& fileName, const BObject* object) const
{
	BFile file(fileName);
	if (!file.open(IO_Write))
	{
		member.message() << "OBC Error : Write file failed : Can't open file " << fileName;
		return false;
	}
	return write(file, object);
}

BObject* BObcDocument::read(const BStream& stream)
{
	member.reset();
	member.stream = (BStream*)&stream;

	int offset = member.stream->offset();
	int size = member.stream->size();
	Chunk chunk;
	if (!member.readBeginChunk(chunk, size))
	{
		message() << "OBC Error : Read chunk faild : sign " << chunk.sign;
		return 0;
	}

	BObject* object = this->create(chunk.sign);
	if (!object)
	{
		message() << "OBC Error : Create object for first chunk [" << chunk.sign << "] failed.";
		return false;
	}

	BArray<member_BObcDocument::readFunc> readFuncs;
	BArray<BObcDocument::readChunk> readChunks;
	for (int i = 0; i < object->typeCount(); i++)
	{
		const char* type = object->typeName(i);
		if (member_BObcDocument::readFunc read = member.readFuncMap(type))
		{
			readFuncs.append(read);
			continue;
		}
		if (BObcDocument::readChunk read = member.readChunkMap(type))
			readChunks.append(read);
	}

	Chunk sub;
	while (member.readBeginChunk(sub, chunk.end))
	{
		bool result = false;
		while (member_BObcDocument::readFunc read = readFuncs.first())
		{
			if ((member.*read)(sub, object))
			{
				result = true;
				break;
			}
			readFuncs.remove(0);
		}
		if (!result && readChunks.size())
		{
			while (BObcDocument::readChunk read = readChunks.first())
			{
				if ((this->*read)(&stream, sub.sign, object))
				{
					result = true;
					break;
				}
				readChunks.remove(0);
			}
		}
		if (!result)
		{
			message() << "OBC Error : [" << member.signTypeMap(chunk.sign) << "] invalid sub chunk " << member.signTypeMap(sub.sign) << '[' << sub.sign << "]\n";
		}
		member.readEndChunk(sub);
	}
	return object;
}
bool BObcDocument::read(const BStream& stream, BObject* object)
{
	member.reset();
	member.stream = (BStream*)&stream;

	int offset = member.stream->offset();
	int size = member.stream->size();
	Chunk chunk;
	if (!member.readBeginChunk(chunk, size))
	{
		member.message() << "OBC Error : Read chunk faild : sign " << chunk.sign;
		return false;
	}

	const BString& type = member.signTypeMap(chunk.sign);
	if (!object->typed(type))
	{
		member.message() << "OBC Error : First chunk [" << chunk.sign << "] is not unknown object - " << object->className();
		return false;
	}

	BArray<member_BObcDocument::readFunc> readFuncs;
	BArray<BObcDocument::readChunk> readChunks;
	for (int i = 0; i < object->typeCount(); i++)
	{
		const char* type = object->typeName(i);
		if (member_BObcDocument::readFunc read = member.readFuncMap(type))
		{
			readFuncs.append(read);
			continue;
		}
		if (BObcDocument::readChunk read = member.readChunkMap(type))
			readChunks.append(read);
	}

	Chunk sub;
	while (member.readBeginChunk(sub, chunk.end))
	{
		bool result = false;
		while (member_BObcDocument::readFunc read = readFuncs.first())
		{
			if ((member.*read)(sub, object))
			{
				result = true;
				break;
			}
			readFuncs.remove(0);
		}
		if (!result && readChunks.size())
		{
			while (BObcDocument::readChunk read = readChunks.first())
			{
				if ((this->*read)(&stream, sub.sign, object))
				{
					result = true;
					break;
				}
				readChunks.remove(0);
			}
		}
		if (!result)
		{
			member.message() << "OBC Error : [" << member.signTypeMap(chunk.sign) << "] invalid sub chunk " << member.signTypeMap(sub.sign) << '[' << sub.sign << "]\n";
		}
		member.readEndChunk(sub);
	}
	return true;
}
bool BObcDocument::write(BStream& stream, const BObject* object) const
{
	if (!object)
		return false;

	member.reset();
	member.stream = (BStream*)&stream;

	BUShort sign = SIGN_NONE;
	BArray<member_BObcDocument::writeFunc> writeFuncs;
	BArray<BObcDocument::writeChunk> writeChunks;
	for (int i = 0; i < object->typeCount(); i++)
	{
		const char* typeName = object->typeName(i);
		if (member_BObcDocument::writeFunc write = member.writeFuncMap(typeName))
		{
			writeFuncs.append(write);
			sign = member.typeSignMap(typeName);
			continue;
		}
		if (BObcDocument::writeChunk write = member.writeChunkMap(typeName))
		{
			writeChunks.append(write);
			sign = member.typeSignMap(typeName);
			continue;
		}
	}
	if (writeFuncs.empty())
	{
		member.message() << "OBC Error : Write object failed : No read func for " << object->className();
		return false;
	}
	AutoChunk chunk(this, sign);
	for (int i = 0; i < writeFuncs.size(); i++)
	{
		member_BObcDocument::writeFunc write = writeFuncs[i];
		if (!(member.*write)(object))
		{
			member.message() << "OBC Error : Write object failed " << object->className();
			return false;
		}
	}
	for (int i = 0; i < writeChunks.size(); i++)
	{
		BObcDocument::writeChunk write = writeChunks[i];
		if (!(this->*write)(&stream, object))
		{
			member.message() << "OBC Error : Write object failed " << object->className();
			return false;
		}
	}
	return true;
}

BString& BObcDocument::message()
{
	return member.message();
}
const BString& BObcDocument::message() const
{
	return member.message();
}
